feat(desktop): Agent Trading Cards — mintable agent-snapshot card PNGs with optional NIP-44 lock - #3278
Open
tlongwell-block wants to merge 19 commits into
Open
feat(desktop): Agent Trading Cards — mintable agent-snapshot card PNGs with optional NIP-44 lock#3278tlongwell-block wants to merge 19 commits into
tlongwell-block wants to merge 19 commits into
Conversation
…rading Cards One OpenAI Responses call (designer model + native image_generation tool) mints a collectible card PNG; the existing agent_snapshot encoder injects the buzz_agent_snapshot chunk so the card IS an importable .agent.png. Boundary rules (agreed with Wren): - Snapshot construction/injection reuses agent_snapshot.rs; no card-only wire format. - Memory exclusion is structural (MemoryLevel::None at build). - 10 MiB .agent.png ceiling enforced on FINAL bytes post-injection. - Round-trip verification decodes final bytes and compares the manifest. - OPENAI_API_KEY resolved via existing env layering (record > persona > global > process), never leaves Rust, never logged. - Card frame template embedded via include_bytes! (generation input only); test gate asserts it decodes with the expected shape. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz> Signed-off-by: npub12gtutshhh76rx0jx697f32f9tffd4hhp3hx58fp4x6u4uemkm7sqf8f757 <5217c5c2f7bfb4333e46d17c98a9255a52dadee18dcd43a43536b95e6776dfa0@buzz.block.builderlab.xyz>
Review finding (Wren): fetch_avatar buffered the full response before checking MAX_AVATAR_FETCH_BYTES, so a missing or dishonest Content-Length could consume unbounded network/memory. Now preflights content_length() and streams with a running cap check that rejects before appending any chunk that would cross 10 MiB — same contract as media_download.rs. Accumulator boundary extracted for unit testing. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
UI lane for Agent Trading Cards: a Sparkles 'Create card' quick action in the agent panel's primary-action row (gated canManagePersona), opening AgentCardMintDialog — optional style notes, one long Rust-side mint call (~2-3 min) with progress copy, preview with reroll, save as .agent.png. NO_OPENAI_KEY errors point the user at provider settings. tauriPersonas gains mintAgentCard/saveAgentCard wrappers; no new backend state — reroll is simply another mint call. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…are path Closes the v1 scope gap Wren flagged: the mint dialog only offered Save. Card preview now includes PersonaShareRecipients + a Send action that routes through useSnapshotSendController — the exact guarded encode → upload (uploadMediaBytes) → send pipeline agent snapshots use, with the same eligibility checkpoints and DM-safety gating. No card-special relay path, per the kickoff contract. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…port) Tyler's encryption requirement, per Wren's wire-contract spec: - New agent_snapshot_envelope.rs: typed outer envelope (format "buzz-agent-snapshot-encrypted", version 1, scheme nip44-v2) riding the same allowlisted buzz_agent_snapshot chunk. Plain cards keep today's encoder byte-for-byte. NIP-44 v2 conversation key over the (owner, agent) pair — both nsecs decrypt, nobody else's. - Inner caps enforced per NIP-AE's local contract: 65,535-byte plaintext cap on the serialized manifest before encryption, envelope JSON/ciphertext caps before base64/decrypt work, decrypted UTF-8 cap before snapshot parsing. - Exact-endpoint key resolution only (owner identity key match, or a hydrated managed-agent record whose pubkey + derived-secret pubkey both equal the embedded agentPubkey) — no trial decryption. All unlock failures surface only the constant LOCKED_CARD_REFUSAL. - mint path: lock flag on mint_agent_card; encrypted round-trip verifies by extracting the chunk, decrypting with the same endpoint key, and comparing logical manifests (not ciphertext). - preview/confirm import wired through decode_snapshot_for_import with owner keys + loaded records; AgentSnapshotImportPreview gains locked: bool (true = unlocked by local keys, full payload disclosed). - fetch_snapshot_bytes validates locked envelopes structurally in transit without decrypting. - Test vectors per Wren's list: owner/agent unlock, unrelated-key refusal, tampered ciphertext, swapped/malformed pubkeys, unknown format/version/scheme, plaintext + ciphertext caps, encrypted final-PNG round trip, plain-card bytes unchanged. - File-size gate: inline test modules split to sibling #[path] files (agent_snapshot_tests.rs, snapshot/tests_locked.rs) following the storage_tests.rs precedent; no new size-gate exceptions. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…sure UI half of the optional locked-card feature: - AgentCardMintDialog: Lock switch (canLock-gated — enabled only when the persona has a linked agent instance/pubkey; disabled with a 'start this agent once' explanation for bare definitions, per Wren's discoverability preference), locked-specific post-mint copy. - UserProfilePanel: canLock = Boolean(managedAgent?.pubkey) threaded into the mint target. - tauriPersonas: mintAgentCard(id, styleNotes?, lock?); MintedAgentCard and AgentSnapshotImportPreview gain locked. - AgentSnapshotImportDialog: locked-card provenance notice rendered only when preview.locked (unlocked by local keys, full decrypted payload disclosed as usual) + rendering test. - e2eBridge preview mock updated with the new preview fields. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…-byte compat vector Closes both findings from Wren's cross-review at 4434d39: 1. parse_canonical_pubkey now requires PublicKey::xonly() to succeed — nostr 0.44's from_hex only decodes 32 bytes and defers lift-x validation, so a non-point like "f"*64 previously passed structural transit/save validation and failed only at unlock. Non-points are now rejected structurally, per the agreed wire contract; the malformed-pubkeys vector asserts structural rejection instead of pinning the deferred-failure behavior. mint_agent_card uses the same canonical check on record.pubkey so a non-point fails BEFORE the API spend. 2. Added the plain-byte compatibility vector that the review claim referenced: plain_encoder_bytes_identical_to_pre_envelope_encoder reimplements the pre-refactor encode_snapshot_png body verbatim and asserts byte-identical output across all three composition paths — placeholder, PNG-avatar (chunk injection ordering), and JPEG transcode. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…BASE_URL for card mints Three follow-ups to the Agent Trading Cards mint flow (#3278): 1. Inline OPENAI_API_KEY capture: new card_mint_key_status command checks (never returns) whether a key resolves through the existing env layering. When none does, the mint dialog swaps to a key-setup panel with a cost disclosure, a pointer to platform.openai.com, and a pointer to the free Export sharing path. Saving writes the key to the global Agent Defaults env — the same single source of truth the mint already reads; no second key store. A NO_OPENAI_KEY mint error now flips into key-setup mode instead of a dead-end toast. 2. Owner-direction primacy: the direction box explicitly accepts card TEXT direction as well as art, and the Responses prompt is restructured so the owner's directions override the default art-style and copy guidance (but never the fixed frame/layout/ text-fidelity contract). Dialog placeholder updated to match. 3. OPENAI_BASE_URL override resolved through the same env layering, unlocking Azure OpenAI and Responses-speaking proxies; trailing slashes normalized. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
… paths, no silent restarts Addresses Wren's review of the card-mint key UX (buzz-agent-trading-cards): 1. Cost disclosure + free alternative now persist in the normal mint form (pre-spend), not only the no-key setup panel: a compact cost note above the Mint action plus a shared FreeSharePathRow. 2. Both directions are actionable: 'Get a key at platform.openai.com' is a real external-link button (openUrl), and the free path is a 'Share without card art' action that closes the mint dialog and opens the existing snapshot export flow (wired at the UserProfilePanel callsite; optional prop so the dialog degrades gracefully elsewhere). 3. Key save no longer restarts running agents: replaced the general set_global_agent_config call (which stops/restarts every running local agent whose effective env changes, and whose restart counts the dialog discarded) with a narrow card_mint_save_openai_key command — validated single-key read-modify-write of the latest on-disk config under the managed-agents store lock, no restarts. The mint re-reads config per call, so no restart is needed; the toast says running agents pick the key up on next restart, and the Agent Defaults query cache is invalidated so the settings editor stays fresh. 4. Azure claim narrowed in the responses_url doc comment: OPENAI_BASE_URL covers Bearer-auth Responses-speaking endpoints/proxies; Azure's URL scheme + api-key header would need a real driver. 5. Owner-copy rule acknowledges the 220-character text-box contract: over-limit owner text is condensed minimally, keeping words and intent — no mutually impossible verbatim-vs-limit instructions. Prompt-contract test updated. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz>
…nu, completion toast, card gallery Four UX fixes for the mint flow, per channel feedback: 1. Non-blocking mint. Mint state moves out of the dialog into `cardMintStore` (useSyncExternalStore module store): the dialog dispatches and closes; a "Minting card… (takes a few minutes)" spinner chip lives in the composer activity rail while the job runs. Multiple concurrent mints each get their own chip. 2. Card download menu. The finished card image carries the same right-click context menu inline channel images have (`MediaContextMenu`), routed through `save_agent_card` — `download_image` is SSRF-pinned to relay /media/ URLs and can't serve a local base64 card. 3. Completion flow. The finished chip is clickable; a success toast with a "View card" action fires when the mint lands; both open the new global `AgentCardViewerDialog` (the preview/share half of the old dialog: save, send, reroll). Successful mints are archived Rust-side to the app data dir (`agents/cards/`, PNG + JSON sidecar + JPEG grid thumb; new `list_agent_cards` / `load_agent_card` commands, archive failures never fail a paid mint), and a "View minted cards" gallery lists them newest-first — click a tile to view/save/share any previous card. 4. Clipped hint text. The 4-line placeholder paragraph no longer overflows the 3-row textarea: short placeholder, examples moved to a visible hint line below the field. Store lifecycle is covered by a 9-case unit suite (injected mintFn, no Tauri); archive file names are shape-validated against dir escape with a unit test. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
* origin/main: fix(desktop): paint community rail full height (#3382) fix(acp): disable goose cron scheduler in managed agent children (#3144) feat(desktop): add custom harness inline from agent dialogs (#3252) chore(compose): remove stale typesense env vars (#3332) feat(desktop): refine agent catalog sharing (#2439) fix(desktop): keep drafts out of the Inbox All view (#3217) docs: restructure DCO guidance into scannable subsection (#3337) Unify mobile loading spinners (#3314) fix(desktop): restore the inbox icon in the sidebar (#3341) fix(desktop): gate codex-acp on a minimum supported version (#3254) feat(cli): add users set-status command for NIP-38 profile status (#3253) fix(composer): scope multiline block formatting (#3246) feat(chart): add relay pod extension points (#3322) Refine mobile attachment picking (#3313) Polish mobile message and search layouts (#3121) Add mobile message image galleries (#3312) Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]> # Conflicts: # desktop/src-tauri/src/commands/personas/snapshot/import.rs # desktop/src-tauri/src/managed_agents/agent_snapshot.rs # desktop/src/features/agents/ui/AgentSnapshotImportDialog.tsx
…mpty state A rejected list_agent_cards query previously fell through to the "No cards yet" branch (data coalesced to []), telling someone their paid, persisted cards did not exist and offering no recovery. The gallery's view-state selection moves into a pure helper (agentCardGalleryViewState) where the error-wins ordering is pinned by tests: rejected queries render an explicit error state that preserves the failure message and offers a Retry action wired to refetch. Review finding by Wren on PR #3278. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
Introduced in the origin/main merge conflict resolution when build_agent_snapshot_import_preview gained a Result return; the closure already returns Result, so the wrapper trips needless_question_mark under -D warnings on CI. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
* origin/main: feat(acp): steer claude-code and codex agents via _session/steering (#3007) feat(desktop): apply WebKit rendering workarounds at startup on Linux (#3271) fix(desktop): stabilize flaky DM expansion E2E ordering assertions (#2004) docs(contributing): document the Linux system libraries just ci requires (#3396) Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
Expose the export flow's memory selector (Agent only / Agent + core memory / Agent + all memories) in the card mint flow, per Tyler's ask on #3278. Rust: mint_agent_card gains an optional memory_level (default "none", parsed by the shared parse_memory_level). Unlike export there is no caller-supplied memory-source pubkey: the source is always the resolved instance itself, so cross-agent memory pairing is structurally impossible, and bare definitions fail with a clear error before any API spend. The level → entries selection is extracted into a shared pure helper (memory_entries_from_listing) used by both paths. The fail-early NIP-44 cap check and round-trip verification now run on the memory-bearing snapshot unchanged; the cap error hints at reducing memory when memory contributed. MintedCard and the archive sidecar record the memory level (legacy sidecars default to none — honest, since memory exclusion used to be structural). UI: mint dialog gets the export-pattern selector (Agent-only default) with an explicit plaintext disclosure when memory is included in an UNLOCKED card — suppressed when the card is locked, because the locked envelope encrypts the manifest and the plaintext warning would be false. The viewer's import disclosure now reflects the card's actual memory level instead of unconditionally claiming "no memories". card.rs and snapshot/tests.rs both crossed the 1000-line gate; their test modules moved to child-module files (card/tests.rs, tests_memory_entries.rs), same layout as snapshot/tests.rs already used. Tests: 5 new Rust (level→entries selection incl. missing-core, sidecar back-compat default, MintedCard wire casing) and 1 new node suite case pinning the store's four-arg mintFn forwarding. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
…size gate main's new base-tree file-size ratchet (#3352) flags UserProfilePanel.tsx and UserProfilePanelSections.tsx: this branch's card-mint wiring pushed both past 1000 lines. Move the primary-action button row components to UserProfilePrimaryActions.tsx and fold the snapshot-export + card-mint dialogs into UserProfilePersonaDialogs.tsx. No behavior change. Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
* origin/main: feat: add explicit entry for claude-opus-5 in model config (#2831) fix(desktop): clear stale thread new-message pill (#3411) fix(ci): ratchet file sizes against the base tree (#3352) chore(ci): bump desktop smoke E2E timeout to 30 minutes (#3409) release(chart): publish 0.1.7 (#3393) Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@buzz.block.builderlab.xyz> Co-authored-by: Tyler Longwell <[email protected]> Signed-off-by: Tyler Longwell <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Agent Trading Cards
"Create Agent Card" action in the agent panel that mints an AI-generated MTG-style trading card PNG which is the agent: the card carries the
buzz_agent_snapshottEXt chunk and is drag-in importable like any snapshot PNG.What's in here
gpt-5.6-solas card designer withgpt-image-2via theimage_generationtool (~2–3 min). Newmint_agent_card/save_agent_cardcommands; preview with reroll; save or send as.agent.pngwith round-trip verification before any bytes leave the app.buzz-agent-snapshot-encryptedenvelope encrypted to the (owner, agent) pair.parse_canonical_pubkeyperforms lift-x curve validation before any API spend; wrong-key decrypt returns a fixed refusal; the plain decoder refuses locked cards.nonein the snapshot, full-manifest import disclosure, API-key hygiene via env layering (record > persona > global > process), fail-early validation ordering (all key/lock/NIP-44-cap checks before Responses spend).Review
Code reviewed by Wren across the full arc; final locked-card cross-review APPROVED 9/9/9 at exactly this head (
64f819dc8), with independent same-SHA verification: Rust lib 1,843/1,843, clippy--all-targets -D warnings, desktop file-size gate.Live-mint evidence (real API, shipping seams, this SHA)
Full evidence (cards + dialog screenshots) posted in the originating thread.